/*
 * Copyright (C) 2011-2018 ShenZhen iBOXSaaS Information Technology Co.,Ltd.
 * 
 * All right reserved.
 * 
 * This software is the confidential and proprietary information of iBOXSaaS Company of China.
 * ("Confidential Information"). You shall not disclose such Confidential Information and shall use it only in
 * accordance with the terms of the contract agreement you entered into with iBOXSaaS inc.
 * 
 */

package com.iboxpay.open.auth.center.service.impl;

import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.iboxpay.open.common.constants.ParamEnums;
import org.iboxpay.open.common.constants.ResultEnums;
import org.iboxpay.open.common.exception.BusinessException;
import org.iboxpay.open.common.exception.ParamValidateException;
import org.iboxpay.open.common.token.RsaUtil;
import org.iboxpay.open.common.token.VerifyResult;
import org.iboxpay.open.common.utils.SnowflakeIdWorker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.iboxpay.common.utils.UUIDUtil;
import com.iboxpay.open.auth.center.config.AuthCenterConfig;
import com.iboxpay.open.auth.center.service.AuthService;
import com.iboxpay.open.auth.center.utils.JedisUtils;
import com.iboxpay.open.auth.center.vo.AuthCodeVo;
import com.iboxpay.open.common.db.dao.AppUserInfoDao;
import com.iboxpay.open.common.db.dao.AuthTokenRecordDao;
import com.iboxpay.open.common.db.dao.AuthTokenRefreshDao;
import com.iboxpay.open.common.db.entity.AppUserInfo;
import com.iboxpay.open.common.db.entity.AuthTokenRecord;
import com.iboxpay.open.common.db.entity.AuthTokenRefresh;

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 * AuthServiceImpl.java
 * @author xuxiao
 * @date 2018/06/25
 */
@Service
@Slf4j
public class AuthServiceImpl implements AuthService {
    @Autowired
    private JedisUtils jedisUtils;
    @Autowired
    private AuthCenterConfig authCenterConfig;
    @Autowired
    private AppUserInfoDao appUserInfoDao;
    @Autowired
    private AuthTokenRecordDao authTokenRecordDao;
    @Autowired
    private AuthTokenRefreshDao authTokenRefreshDao;
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;

    @Override
    public String authUserConfirm(Map<String, Object> requestMap) {
        AuthCodeVo authCodeVo = new AuthCodeVo();
        authCodeVo.setAppId(Long.valueOf((String)requestMap.get(ParamEnums.appId.getCode())));
        authCodeVo.setScope((String)requestMap.get(ParamEnums.scope.getCode()));
        authCodeVo.setUserId((String)requestMap.get(ParamEnums.userId.getCode()));
        // 生成authCode
        String authCode = UUIDUtil.getUUID();
        // 存储到redis
        jedisUtils.setExt(authCode, authCodeVo);
        jedisUtils.expire(authCode, authCenterConfig.getAuthCodeExpire());
        return authCode;
    }

    @Override
    public Map<String, Object> authUserAccessToken(Map<String, Object> requestMap) {
        Map<String, Object> result = new HashMap<>();
        String authCode = (String)requestMap.get(ParamEnums.authCode.getCode());
        // 1、根据authCode从redis取值
        AuthCodeVo authCodeVo = jedisUtils.getExt(authCode);
        // 2、authCode不存在返回结果
        if (authCodeVo == null) {
            throw new BusinessException(ResultEnums.authCodeNotExists);
        }
        // 3、检查openId是否存在，不存在则生成
        Long openId = obtainOpenId(authCodeVo);
        // 4、创建token_id，生成access_token和refresh_token
        Long tokenId = snowflakeIdWorker.nextId();
        int accessTokenExp = authCenterConfig.getAccessTokenExp();
        int refreshTokenExp = authCenterConfig.getRefreshTokenExp();
        // 生成accessToken
        generateTokenAndFillResult(result, tokenId, authCodeVo.getScope(), authCodeVo.getAppId(), openId,
            ParamEnums.accessToken, accessTokenExp);
        // 生成refreshToken
        generateTokenAndFillResult(result, tokenId, authCodeVo.getScope(), authCodeVo.getAppId(), openId, 
            ParamEnums.refreshToken, refreshTokenExp);
        // 5、数据入库
        // TODO tokenLevel转成枚举
        generateTokenRecord(result, authCodeVo.getAppId(), openId, tokenId, authCode, (byte)1);
        // 6、组装返回结果
        result.put(ParamEnums.resultCode.getCode(), ResultEnums.success.getCode());
        result.put(ParamEnums.errorDesc.getCode(), "");
        result.put(ParamEnums.openId.getCode(), openId);
        return result;
    }
    
    /**
     * 生成token并设置返回对象
     * @param result
     * @param tokenId
     * @param scope
     * @param appId
     * @param token
     * @param tokenExp
     */
    private void generateTokenAndFillResult(Map<String, Object> result, Long tokenId, String scope, Long appId, Long openId, ParamEnums token,
        int tokenExp) {
        Map<String, Object> tokenParam = tokenParam(tokenId, scope, appId, openId, token, tokenExp);
        result.put(token.getCode(), generateToken(tokenParam, tokenExp));
        result.put(tokenExp(token).getCode(), tokenExp);
        result.put(ParamEnums.scope.getCode(), scope);
    }

    /**
     * 生成token参数
     * 
     * @param tokenId
     * @param scope
     * @param appId
     * @return
     */
    private Map<String, Object> tokenParam(Long tokenId, String scope, Long appId, Long openId, ParamEnums token,
        int tokenExp) {
        Map<String, Object> tokenParam = new HashMap<>();
        tokenParam.put(ParamEnums.tokenId.getCode(), tokenId);
        tokenParam.put(ParamEnums.scope.getCode(), scope);
        tokenParam.put(ParamEnums.appId.getCode(), appId);
        tokenParam.put(ParamEnums.openId.getCode(), openId);
        tokenParam.put(ParamEnums.tokenType.getCode(), token.getCode());
        tokenParam.put(tokenExp(token).getCode(), tokenExp);
        return tokenParam;
    }
    
    private String generateToken(Map<String, Object> tokenParam, int tokenExp) {
        String tokenValue = "";
        try {
            tokenValue = RsaUtil.getToken(tokenParam, authCenterConfig.getPrivateKeyStr(), tokenExp);
        } catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
            log.error(e.getMessage(), e);
            throw new BusinessException(ResultEnums.tokenGenerateError);
        }
        return tokenValue;
    }
    /**
     * 获取tokenExp类型
     * @param token
     * @return
     */
    private ParamEnums tokenExp(ParamEnums token) {
        switch(token) {
            case accessToken:
                return ParamEnums.accessTokenExp;
            case refreshToken:
                return ParamEnums.refreshTokenExp;
            default :
                throw new BusinessException(ResultEnums.tokenTypeIncorrect);
        }
    }

    /**
     * 获取openId
     * 
     * @param authCodeVo
     * @return
     */
    private Long obtainOpenId(AuthCodeVo authCodeVo) {
        AppUserInfo appUserInfo = appUserInfoDao.selectByAppIdAndUserId(authCodeVo.getAppId(), authCodeVo.getUserId());
        if (appUserInfo == null) {
            Date current = new Date();
            appUserInfo = new AppUserInfo();
            appUserInfo.setOpenId(snowflakeIdWorker.nextId());
            appUserInfo.setAppId(authCodeVo.getAppId());
            appUserInfo.setUserId(authCodeVo.getUserId());
            appUserInfo.setUpdateTime(current);
            appUserInfo.setCreateTime(current);
            appUserInfoDao.insertSelective(appUserInfo);
        }
        return appUserInfo.getOpenId();
    }

    /**
     * 生成token记录
     * 
     * @param result
     * @param appId
     * @param openId
     * @param tokenId
     * @param authCode
     */
    private void generateTokenRecord(Map<String, Object> result, Long appId, Long openId, Long tokenId, String authCode,
        byte tokenLevel) {
        AuthTokenRecord authTokenRecord = new AuthTokenRecord();
        authTokenRecord.setTokenId(tokenId);
        authTokenRecord.setAccessToken((String)result.get(ParamEnums.accessToken.getCode()));
        authTokenRecord.setAccessTokenExp((int)result.get(ParamEnums.accessTokenExp.getCode()));
        authTokenRecord.setAppId(appId);
        authTokenRecord.setAuthCode(authCode);
        authTokenRecord.setOpenId(openId);
        authTokenRecord.setRefreshToken((String)result.get(ParamEnums.refreshToken.getCode()));
        authTokenRecord.setRefreshTokenExp((int)result.get(ParamEnums.refreshTokenExp.getCode()));
        authTokenRecord.setTokenLevel(tokenLevel);
        Date now = new Date();
        authTokenRecord.setTokenEnactTime(now);
        authTokenRecord.setCreateTime(now);
        authTokenRecord.setUpdateTime(now);
        authTokenRecordDao.insertSelective(authTokenRecord);
    }

    @Override
    public Map<String, Object> authAppAccessToken(Map<String, Object> requestMap) {
        Map<String, Object> result = new HashMap<>();
        String scope = (String)requestMap.get(ParamEnums.scope.getCode());
        Long appId = Long.valueOf((String)requestMap.get(ParamEnums.appId.getCode()));
        // 3、创建token_id，生成access_token和refresh_token
        Long tokenId = snowflakeIdWorker.nextId();
        int accessTokenExp = authCenterConfig.getAppAccessTokenExp();
        int refreshTokenExp = authCenterConfig.getAppRefreshTokenExp();
        // 生成accessToken
        generateTokenAndFillResult(result, tokenId, scope, appId, 0L, ParamEnums.accessToken, accessTokenExp);
        // 生成refreshToken
        generateTokenAndFillResult(result, tokenId, scope, appId, 0L, ParamEnums.refreshToken, refreshTokenExp);
        // 5、数据入库
        generateTokenRecord(result, appId, 0L, tokenId, "", (byte)2);
        // 6、组装返回结果
        result.put(ParamEnums.resultCode.getCode(), ResultEnums.success.getCode());
        result.put(ParamEnums.errorDesc.getCode(), "");
        return result;
    }

    private void generateTokenRefresh(Map<String, Object> result, Long tokenId) {
        AuthTokenRefresh tokenRefresh = new AuthTokenRefresh();
        tokenRefresh.setTokenId(tokenId);
        tokenRefresh.setRefreshId(snowflakeIdWorker.nextId());
        tokenRefresh.setAccessToken((String)result.get(ParamEnums.accessToken.getCode()));
        tokenRefresh.setAccessTokenExp((Integer)result.get(ParamEnums.accessTokenExp.getCode()));
        Date now = new Date();
        tokenRefresh.setTokenEnactTime(now);
        tokenRefresh.setCreateTime(now);
        tokenRefresh.setUpdateTime(now);
        authTokenRefreshDao.insertSelective(tokenRefresh);
    }

    @Override
    public Map<String, Object> authRefreshToken(Map<String, Object> requestMap) {
        Map<String, Object> result = new HashMap<>();
        // 1、解析token
        String refreshToken = (String)requestMap.get(ParamEnums.refreshToken.getCode());
        VerifyResult<Claims> verifyResult = null;
        try {
            verifyResult = RsaUtil.verify(refreshToken, authCenterConfig.getPublickeyStr());
            if (!verifyResult.isVaild()) {
                throw new ParamValidateException(verifyResult.getResultCode());
            }
        } catch (Exception e) {
            throw new ParamValidateException(ResultEnums.tokenSystemError);
        }
        // 2、验证tokenId
        Long tokenId = verifyResult.getResult().get(ParamEnums.tokenId.getCode(), Long.class);
        Long appId = verifyResult.getResult().get(ParamEnums.appId.getCode(), Long.class);
        String scope = verifyResult.getResult().get(ParamEnums.scope.getCode(), String.class);
        Long openId = verifyResult.getResult().get(ParamEnums.openId.getCode(), Long.class);
        AuthTokenRecord authTokenRecord = authTokenRecordDao.selectByPrimaryKey(tokenId);
        if (authTokenRecord == null || !refreshToken.equals(authTokenRecord.getRefreshToken())) {
            throw new BusinessException(ResultEnums.refreshTokenNotExists);
        }
        // 3、生成新token
        generateTokenAndFillResult(result, tokenId, scope, appId, openId, ParamEnums.accessToken, authCenterConfig.getAppAccessTokenExp());
        // 4、数据入库
        generateTokenRefresh(result, tokenId);
        // 5、返回结果
        result.put(ParamEnums.resultCode.getCode(), ResultEnums.success.getCode());
        result.put(ParamEnums.errorDesc.getCode(), "");
        result.put(ParamEnums.refreshToken.getCode(), authTokenRecord.getRefreshToken());
        result.put(ParamEnums.refreshTokenExp.getCode(), authTokenRecord.getRefreshTokenExp());
        return result;
    }

}
